#include "PDFWriter.hpp"
#include <iostream>
#include "common/logger.h"
#include "pdf/pdfw.hpp"

namespace  AMCAX::Exchange {

    namespace PDFLayout
    {
        void CreatePDFLayout(const Dtk_string & inFileName, Dtk_Int32 artworkIndex,
        std::map<Dtk_Node::NodeDataTypeEnum, Dtk_Size_t> &inModelInventory);
    }


    void PDFWriter::SetInputFile(Dtk_string inputFile)
    {
        m_inputFileName = inputFile;
    }

    Dtk_ErrorStatus PDFWriter::WriteInit(Dtk_string dumpFile, const Dtk_string& outputFile)
    {
        return WriteInit(dumpFile, outputFile, 0);
    }

    Dtk_ErrorStatus PDFWriter::WriteInit(Dtk_string dumpFile, const Dtk_string& outputFile, int option)
    {
        m_outputFileName = outputFile;
        std::cout << "Output file is : " << m_outputFileName << std::endl;

        Dtk_ErrorStatus stErrorPdf;
        Pdfw::WriteOptions options;

        // Pdf options :
        // We choose between PRC or U3D 3D format
        options.WritePRC = DTK_TRUE;
        // We will generate our own layout
        options.Layout.CreateAutomaticLayout = DTK_FALSE;
    #ifndef Linux
        // We choose the font to use
        options.Layout.TTF_Font = "C:/Windows/Fonts/arial.ttf";
        options.Layout.RasterizeTexts = DTK_FALSE;
    #endif
        // We handle line thickness
        options.Layout.WriteLineThickness = DTK_TRUE;
        // We set a transparent background
        options.TransparentBackground = DTK_TRUE;

        // Initializing the writer
        stErrorPdf = Pdfw::InitWrite(m_outputFileName, "", NULL, options);

        if (stErrorPdf == dtkNoError)
            std::cout << "Creating Pdf file... " << m_outputFileName.c_str() << std::endl;
        else
            std::cout << "Warning : Pdf Write failed : " << dtkTypeError(stErrorPdf).c_str() << std::endl;

        //**************************************************************************************************************//
        //layout
        //**************************************************************************************************************//
        // Page creation
        Pdfw::Layout::InitPage();

        // Size of Page 
        Pdfw::Layout::SetCurrentPageSize(Pdfw::Layout::SIZE_A5, Pdfw::Layout::LANDSCAPE);

        // PDF 3D Artwork initialization
        Pdfw::Layout::Init3DArtwork(m_inputFileName.filename());

        return dtkNoError;
    }


    Dtk_ErrorStatus PDFWriter::WriteEnd()
    {
        Dtk_UInt32 artworkIndex = 0;
        Pdfw::Layout::End3DArtwork(artworkIndex);
        //PDFLayout::CreatePDFLayout(m_inputFileName, artworkIndex, modelInventory);
        modelInventory.clear();
        Pdfw::Layout::EndPage();
        Pdfw::EndWrite();

        return dtkNoError;
    }

    void PDFWriter::InitNode(Dtk_NodePtr inNode)
    {
        Dtk_ID pdfID;
        Dtk_string NodeName = inNode->Name();
        Dtk_InfoPtr nodeInfo = inNode->GetInfos();
        Dtk_bool nodeVisi = nodeInfo->GetBlankedStatus() ? DTK_FALSE : DTK_TRUE;
        Pdfw::InitNode(NodeName, Dtk_transfo(), nodeVisi, pdfID);
        if (nodeInfo.IsNotNULL())
            Pdfw::AddNodeRenderInfo(nodeInfo->GetRenderInfos());
    }
    
    void PDFWriter::EndNode()
    {
          Pdfw::EndNode();
    }


    
    void PDFWriter::WriteAxisSystem(const Dtk_AxisSystemPtr& inAxis)
    {
        WriteEntity(Dtk_EntityPtr::DtkDynamicCast(inAxis));
    }

    void PDFWriter::WriteFdt(const Dtk_FdtPtr& inFdt)
    {
        WriteEntity(Dtk_EntityPtr::DtkDynamicCast(inFdt));
    }
    

    void PDFWriter::WriteMesh(Dtk_MeshPtr& inMeshToWrite, Dtk_tab<Dtk_ID>& outIds)
    {
        WriteEntity(Dtk_EntityPtr::DtkDynamicCast(inMeshToWrite));
    }

    void PDFWriter::WriteEntity(Dtk_EntityPtr inEntity)
    {   
        Pdfw::WriteEntity(inEntity);
        if (inEntity->get_type_detk() == DTK_TYPE_BODY)
            modelInventory[Dtk_Node::BodyType]++;
        if (inEntity->get_type_detk() == DTK_TYPE_MESH)
            modelInventory[Dtk_Node::MeshType]++;
        if (inEntity->get_type_detk() == DTK_TYPE_FDT)
            modelInventory[Dtk_Node::FdtType]++;
    }

    namespace PDFLayout
    {
        
        DtkErrorStatus PdfWritePicture(Dtk_string imageFullPathName, Dtk_Double64 pictureBBox[4])
        {
            DtkErrorStatus errPict = Pdfw::Layout::WritePicture(imageFullPathName, pictureBBox);
            if (errPict == dtkErrorFileNotExist)
                std::cout << "Image File does not exist (" << imageFullPathName.c_str() << ")" << std::endl;

            return errPict;
        }

        void PdfWriteDtk_stringAsCompositeText(const Dtk_string &inString, Dtk_RGB textColor, Dtk_pnt location, Dtk_Double64 charWidth, Dtk_Double64 charHeight)
        {
            //  Creation of Text info
            Dtk_InfoPtr text_info = Dtk_Info::create();
            //Setting text color to blue
            text_info->SetColor(textColor);
            //Setting text style
            Dtk_Double64 slant = 1.;
            Dtk_TextStyle text_style;
            Dtk_CompositeText compoText;
            Dtk_Double64 XLen = inString.len() * charWidth, YLen = charHeight;
            Dtk_dir XDir(1., 0., 0.), YDir(0., 0., 1.);
            Dtk_Oriented2dBBox inbox(location, XLen, YLen, XDir, YDir);
            DTK_Text_type text_type = dtk_text_type_undefined;
            Dtk_bool horizontalOrVertical = 1;

            Dtk_Int32 pos = inString.find_substring("\n");
            if (pos > 0)
            {
                Dtk_string leftString, rightString;
                leftString = inString.Substring(0, pos);
                rightString = inString.Substring(pos, inString.len());
                Dtk_Text leftText = Dtk_Text(leftString, inbox, inbox, slant, horizontalOrVertical, text_type, text_info, text_style);
                compoText.AddText(leftText);
                Dtk_transfo transfo;
                transfo.setOrigin(Dtk_pnt(inbox.GetBottomLeftPoint().x() - inbox.GetYLength(), inbox.GetBottomLeftPoint().y()));
                inbox.Transform(transfo);
                Dtk_Text rightText = Dtk_Text(rightString, inbox, inbox, slant, horizontalOrVertical, text_type, text_info, text_style);
                compoText.AddText(rightText);
            }
            else
            {
                Dtk_Text text = Dtk_Text(inString, inbox, inbox, slant, horizontalOrVertical, text_type, text_info, text_style);
                compoText.AddText(text);
            }

            Pdfw::Layout::WriteCompositeText(compoText);
        }

        void PdfWriteArray(Dtk_Size_t inNumLines, Dtk_Size_t inNumColumns,
            Dtk_Double64 inCellWidth, Dtk_Double64 inCellHeight,
            Dtk_tab< Dtk_tab< Dtk_string> > &inTabStrings,
            Dtk_Double64 inCharWidth, Dtk_Double64 inCharHeight,
            Dtk_tab < Dtk_tab < Dtk_pnt> > &outTabPoints,
            Dtk_pnt inOrigin,
            Dtk_RGB inColor)
        {
            Dtk_tab<Dtk_PolylinePtr> tabPolylines;
            Dtk_Size_t i, j;
            Dtk_pnt location;
            Dtk_dir xDir(1.0, 0.0);
            Dtk_dir yDir(0.0, 1.0);
            Dtk_Double64 lineThickness = 1.0;
            outTabPoints.resize(inNumLines + 1);
            location = inOrigin;
            outTabPoints[0].resize(inNumColumns + 1);
            for (j = 0; j < inNumColumns + 1; j++)
            {
                outTabPoints[0][j] = location + xDir * (j*inCellWidth);
            }

            for (i = 0; i < inNumLines; i++)
            {
                location -= yDir * inCellHeight;

                outTabPoints[i + 1].resize(inNumColumns + 1);
                for (j = 0; j < inNumColumns + 1; j++)
                {
                    outTabPoints[i + 1][j] = (location + xDir * (j * inCellWidth));
                }
                location[0] = inOrigin[0];
            }

            for (i = 0; i < inNumLines; i++)
            {
                Dtk_Double64 bbox[4];
                bbox[0] = outTabPoints[i + 1][0].x();
                bbox[1] = outTabPoints[i + 1][0].y();
                bbox[2] = outTabPoints[i][1].x();
                bbox[3] = outTabPoints[i][1].y();
                Pdfw::Layout::FillRectangle(bbox, Dtk_RGB(200, 200, 200));
                bbox[0] = outTabPoints[i + 1][1].x();
                bbox[1] = outTabPoints[i + 1][1].y();
                bbox[2] = outTabPoints[i][2].x();
                bbox[3] = outTabPoints[i][2].y();
                Pdfw::Layout::FillRectangle(bbox, Dtk_RGB(210, 210, 255));
            }

            location = inOrigin;
            for (i = 0; i < inNumLines; i++)
            {
                location -= yDir * inCellHeight;
                for (j = 0; j < inNumColumns; j++)
                {
                    Dtk_string &field = inTabStrings[i][j];
                    Dtk_Double64 offset = inCellHeight / 6;
                    PdfWriteDtk_stringAsCompositeText(field, Dtk_RGB(0, 0, 0), location + xDir*offset + yDir*offset, inCharWidth, inCharHeight);
                    location += xDir * inCellWidth;
                }
                location[0] = inOrigin[0];
            }

            Dtk_PolylinePtr polyline = Dtk_Polyline::Create(outTabPoints[0][0], outTabPoints[0][inNumColumns]);
            polyline->AddPoint(outTabPoints[inNumLines][inNumColumns]);
            polyline->AddPoint(outTabPoints[inNumLines][0]);
            polyline->AddPoint(outTabPoints[0][0]);
            polyline->AddPoint(outTabPoints[0][inNumColumns]);
            polyline->info() = Dtk_Info::create();
            polyline->info()->SetCurveThickNessInMM(lineThickness);
            tabPolylines.push_back(polyline);

            //lines
            lineThickness = 0.2;
            for (i = 0; i < inNumLines; i++)
            {
                polyline = Dtk_Polyline::Create(outTabPoints[i + 1][0], outTabPoints[i + 1][1]);
                for (j = 2; j < inNumColumns + 1; j++)
                {
                    polyline->AddPoint(outTabPoints[i + 1][j]);
                }
                polyline->info() = Dtk_Info::create();
                polyline->info()->SetCurveThickNessInMM(lineThickness);
                tabPolylines.push_back(polyline);
            }
            //columns
            lineThickness = 1.0;
            for (i = 0; i < inNumColumns; i++)
            {
                polyline = Dtk_Polyline::Create(outTabPoints[0][i + 1], outTabPoints[1][i + 1]);
                for (j = 2; j < inNumLines + 1; j++)
                {
                    polyline->AddPoint(outTabPoints[j][i + 1]);
                }
                polyline->info() = Dtk_Info::create();
                polyline->info()->SetCurveThickNessInMM(lineThickness);
                tabPolylines.push_back(polyline);
            }

            Pdfw::Layout::WritePolylines(tabPolylines);
        }
            
        Dtk_pnt PdfWriteCartridge(const Dtk_string & inFileName,
            std::map<Dtk_Node::NodeDataTypeEnum, Dtk_Size_t> &inModelInventory)
        {
            Dtk_API * MyAPI = Dtk_API::GetAPI();
            Dtk_tab < Dtk_tab < Dtk_string> > tabStrings;
            Dtk_tab < Dtk_tab < Dtk_pnt> > tabPoints;
            Dtk_Double64 charHeight = 5, charWidth = 4, cellHeight, cellWidth;
            Dtk_Size_t numLines = 5, numColumns = 2;
            Dtk_Size_t i;
            Dtk_pnt origin;
            Dtk_dir xDir(1, 0, 0), yDir(0, 1, 0);
            Dtk_Double64 margin = 15.;
            Dtk_RGB color(200, 200, 200);
            Dtk_RGB textColor(0, 0, 128);
            Dtk_pnt location(20., 90.0);

            cellHeight = charHeight + 13;
            cellWidth = Pdfw::Layout::GetCurrentPageWidth() / 3;

            origin = Dtk_pnt(Pdfw::Layout::GetCurrentPageWidth() - margin - (numColumns*cellWidth), margin + (numLines*cellHeight));

            //**************************************************************************************************************//
            // Background Picture creation
            //**************************************************************************************************************//
            Dtk_string imageFullPathName = "../InputImages/background.jpg";
            Dtk_Double64 pictureBBox[4];
            pictureBBox[0] = 0.0;
            pictureBBox[1] = 0.0;
            pictureBBox[2] = Pdfw::Layout::GetCurrentPageWidth();
            pictureBBox[3] = Pdfw::Layout::GetCurrentPageHeight();
            PdfWritePicture(imageFullPathName, pictureBBox);


            //**************************************************************************************************************//
            // Logo creation
            //**************************************************************************************************************//
            imageFullPathName = "../InputImages/logodtk.bmp";
            pictureBBox[0] = 20.0;
            pictureBBox[1] = 20.0;
            pictureBBox[2] = 70.0;
            pictureBBox[3] = 70.0;
            PdfWritePicture(imageFullPathName, pictureBBox);

            //**************************************************************************************************************//
            // Date of creation
            //**************************************************************************************************************//
            Dtk_string dtkVersion = MyAPI->GetVersion();
            std::time_t t = std::time(0);
            Dtk_string dateString = Dtk_string("Files converted by");
            location = Dtk_pnt(78., 52.);
            PdfWriteDtk_stringAsCompositeText(dateString, textColor, location, charWidth, charHeight);
            dateString = "Datakit API (V" + dtkVersion + ") on :";
            location -= 10 * yDir;
            PdfWriteDtk_stringAsCompositeText(dateString, textColor, location, charWidth, charHeight);
            char buffer[26];  // Buffer to store the formatted time
        #if defined(_WIN32) || defined(_WIN64)    
            ctime_s(buffer, sizeof(buffer), &t);
        #else
            ctime_r(&t, buffer);
        #endif
            dateString = buffer;
            location -= 10 * yDir;
            PdfWriteDtk_stringAsCompositeText(dateString, textColor, location, charWidth, charHeight);


            //**************************************************************************************************************//
            // Frame
            //**************************************************************************************************************//
            Dtk_tab<Dtk_PolylinePtr>tabPolylines;
            Dtk_PolylinePtr polyline = Dtk_Polyline::Create(Dtk_pnt(margin, margin), Dtk_pnt(margin, Pdfw::Layout::GetCurrentPageHeight() - margin));
            polyline->AddPoint(Dtk_pnt(Pdfw::Layout::GetCurrentPageWidth() - margin, Pdfw::Layout::GetCurrentPageHeight() - margin));
            polyline->AddPoint(Dtk_pnt(Pdfw::Layout::GetCurrentPageWidth() - margin, margin));
            polyline->AddPoint(Dtk_pnt(margin, margin));
            polyline->AddPoint(Dtk_pnt(margin, Pdfw::Layout::GetCurrentPageHeight() - margin));
            polyline->info() = Dtk_Info::create();
            polyline->info()->SetCurveThickNessInMM(2);
            tabPolylines.push_back(polyline);


            //**************************************************************************************************************//
            // Array with Properties
            //**************************************************************************************************************//

            //filling of tabstrings
            tabStrings.resize(numLines);
            for (i = 0; i < numLines; i++)
            {
                tabStrings[i].resize(numColumns);
            }

            Dtk_string numBodies;
            Dtk_string numMeshes;
            Dtk_string numPMIs;
            Dtk_string numDrawings;

            numBodies.add_int((int)inModelInventory[Dtk_Node::BodyType]);
            numMeshes.add_int((int)inModelInventory[Dtk_Node::MeshType]);
            numPMIs.add_int((int)inModelInventory[Dtk_Node::FdtType]);
            numDrawings.add_int((int)inModelInventory[Dtk_Node::DrawingType]);

            tabStrings[0][0] = "Inventory"; tabStrings[0][1] = inFileName.filename() + inFileName.extension();
            tabStrings[1][0] = "Number of processed BODIES"; tabStrings[1][1] = numBodies;
            tabStrings[2][0] = "Number of processed MESHES"; tabStrings[2][1] = numMeshes;
            tabStrings[3][0] = "Number of processed 3D PMIS"; tabStrings[3][1] = numPMIs;
            tabStrings[4][0] = "Number of processed DRAWINGS"; tabStrings[4][1] = numDrawings;

            origin[0] = 206;
            origin[1] = 85;
            cellHeight = charHeight + 9;
            cellWidth = (Pdfw::Layout::GetCurrentPageWidth() - origin[0] - margin) / 2;
            PdfWriteArray(numLines, numColumns, cellWidth, cellHeight, tabStrings, charWidth, charHeight, tabPoints, origin, color);

            Pdfw::Layout::WritePolylines(tabPolylines);

            return origin;
        }

        void CreatePDFLayout(const Dtk_string & inFileName, Dtk_Int32 artworkIndex,
            std::map<Dtk_Node::NodeDataTypeEnum, Dtk_Size_t> &inModelInventory)
        {

            //**************************************************************************************************************//
            // Cartridge
            //**************************************************************************************************************//
            Dtk_pnt origin = PdfWriteCartridge(inFileName, inModelInventory);


            //**************************************************************************************************************//
            // 3D annotation creation
            //**************************************************************************************************************//
            if (artworkIndex > -1)
            {//3D Annotation
            //**************************************************************************************************************//
            // Computing annotation Bounding Box
            //**************************************************************************************************************//
                Dtk_Double64 margin = 16.0, bottom, top, left, right;
                bottom = origin[1];
                left = margin;
                right = Pdfw::Layout::GetCurrentPageWidth() - margin;
                top = Pdfw::Layout::GetCurrentPageHeight() - margin;
                Dtk_Double64 annotBBox[4] = { left, bottom, right, top };
                //WARNING : no text must be added to current page after this 3d annotation or the result pdf file will have some errors in adobe reader....
                Pdfw::Layout::Create3DAnnotation(artworkIndex, annotBBox);
            }
        }

    }
}
